package com.gusto.engine.clusterant.algorithms.antsclustering;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.log4j.Logger;

import com.gusto.engine.clusterant.access.DistanceDAO;
import com.gusto.engine.clusterant.access.PlanDAO;
import com.gusto.engine.clusterant.model.Point;

/**
 * <p>A 2 dimensions Plan for running the Ants.</p>
 * 
 * @author amokrane.belloui@gmail.com
 *
 */
public class Plan {
	
	private Logger log = Logger.getLogger(getClass());
	
	private PlanDAO planDAO;
	private DistanceDAO distanceDAO;
	
	private int rows, cols;
	private Map<Integer, Map<Integer, Object>> rowsMap;
	private List<Point> points;
	
	public Plan(int rows, int cols) {
		// Initialize parameters
		this.rows = rows;
		this.cols = cols;
		// Initialize Data Structure
		rowsMap = new HashMap<Integer, Map<Integer, Object>>();
		for (int i = 0; i < rows; i++) {
			rowsMap.put(i, new HashMap<Integer, Object>());
		}
		this.points = new ArrayList<Point>();
	}
	
	
	public Plan(PlanDAO planDAO, DistanceDAO distanceDAO, int rows, int cols) {
		this(rows, cols);
		// Initialize parameters and DAOs
		this.planDAO = planDAO;
		this.distanceDAO = distanceDAO;
		
		/*
		this.rows = rows;
		this.cols = cols;
		// Initialize Data Structure
		rowsMap = new HashMap<Integer, Map<Integer, Object>>();
		for (int i = 0; i < rows; i++) {
			rowsMap.put(i, new HashMap<Integer, Object>());
		}
		this.points = new ArrayList<Point>();
		*/
	}
	
	public String toString() {
		String result = "\n";
		for (int i = 0; i < rows; i++) {
			for (int j = 0; j < cols; j++) {
				String cell = ""+(!isEmpty(i, j) ? (getObject(i, j)!=null ? getObject(i, j) : "X") : "-");
				if (cell.equals("-")) { cell = " - "; }
				if (cell.equals("X")) { cell = " X "; }
				if (cell.length()==1) { cell = " " + cell + " "; }
				if (cell.length()==2) { cell = " " + cell; }
				//if (cell.length()==3) { cell = cell; }
				result += cell;
			}
			result += "\n";
		}
		return result;
	}
	
	public List<Point> getPoints() {
		return points;
	}

	public int getRows() {
		return rows;
	}

	public int getCols() {
		return cols;
	}
	
	public void initialize(List<Object> points, int minDist) {
		if (planDAO != null) {
			planDAO.removeAllObjects();
		}
		for (Object p : points) {
			Point point = this.putObjectRandom(p, minDist);
			log.info("Point: " + point);
		}
	}
	
	public void loadPoints() {
		if (planDAO != null) {
			List<Point> pts = planDAO.loadPoints();
			
			for (Point p : pts) {
				try {
					this.removeObject(p.getRow(), p.getCol());
				}
				catch (Exception ex) {
				}
				this.putObject(p.getRow(), p.getCol(), p.getObject());
			}
		}
	}
	
	public synchronized boolean isEmpty(int row, int col) {
		return !rowsMap.get(row).containsKey(col);
	}
	
	public synchronized Object getObject(int row, int col) {
		return rowsMap.get(row).get(col);
	}
	
	public synchronized Point putObject(int row, int col, Object object) {
		rowsMap.get(row).put(col, object);
		Point point = new Point(row, col, object);
		points.add(point);
		if (planDAO != null) {
			planDAO.putObject(row, col, object);
		}
		return point;
	}
	
	public synchronized Point putObjectIfEmpty(int row, int col, Object object) {
		if (isEmpty(row, col)) {
			Point p = putObject(row, col, object);
			return p;
		}
		return null;
	}
	
	private int random(int max) {
		return Integer.parseInt(Math.round(Math.random() * (max-1)) + "");
	}
	
	public Point putObjectRandom(Object object, int minDist) {
		int row = 0;
		int col = 0;
		
		boolean canPut = false;
		while (!canPut) {
			row = random(rows);
			col = random(cols);
			canPut = isEmpty(row, col) && getNeighbors(row, col, minDist).size() == 0;
		}
		return putObject(row, col, object);
	}
	
	public synchronized void removeObject(int row, int col) {
		rowsMap.get(row).remove(col);
		points.remove(new Point(row, col));
		if (planDAO != null) {
			planDAO.removeObject(row, col);
		}
	}
	
	public synchronized boolean removeObjectIfNotEmpty(int row, int col) {
		if (!isEmpty(row, col)) {
			removeObject(row, col);
			return true;
		}
		return false;
	}
	
	public List<Neighbor> getNeighbors(int row, int col, int perception) {
		List<Neighbor> neighbors = new ArrayList<Neighbor>();
		
		for (int i = row - perception; i <= row + perception; i++) {
			for (int j = col - perception; j <= col + perception; j++) {
				if (! (i==row && j==col )) {
					if (i>=0 && i<rows && j>=0 && j<cols) {
						int ii = Math.abs(i - row);
						int jj = Math.abs(j - col);
						Double dist = 1 / Math.sqrt( Math.pow(ii, 2) + Math.pow(jj, 2) );
						if (!isEmpty(i, j)) {
							neighbors.add(new Neighbor(getObject(i, j), dist));
							log.debug("+ " + i + " " + j + " [" + dist + "]");
						}
					}
				}
			}
		}
		
		return neighbors;
	}
	
	
	/*
	public List<Neighbor> getNeighbors(int row, int col) {
		List<Neighbor> neighbors = new ArrayList<Neighbor>();
		
		Double NEAR_1 = 1.0;
		Double NEAR_2 = 0.7;
		Double NEAR_3 = 0.5;
		
		if (row>1) {
			if (!isEmpty(row-2, col-2) && (col>1))      {neighbors.add(new Neighbor(getObject(row-2, col-2), NEAR_3));}
			if (!isEmpty(row-2, col-1) && (col>0))      {neighbors.add(new Neighbor(getObject(row-2, col-1), xx));}
			if (!isEmpty(row-2, col))                   {neighbors.add(new Neighbor(getObject(row-2, col), xx));}
			if (!isEmpty(row-2, col+1) && (col<cols-1)) {neighbors.add(new Neighbor(getObject(row-2, col+1), xx));}
			if (!isEmpty(row-2, col+2) && (col<cols-2)) {neighbors.add(new Neighbor(getObject(row-2, col+2), xx));}
		}
		
		if (row>0) {
			if (!isEmpty(row-1, col-2) && (col>1))      {neighbors.add(new Neighbor(getObject(row-1, col-2), xx));}
			if (!isEmpty(row-1, col-1) && (col>0))      {neighbors.add(new Neighbor(getObject(row-1, col-1), xx));}
			if (!isEmpty(row-1, col))                   {neighbors.add(new Neighbor(getObject(row-1, col), xx));}
			if (!isEmpty(row-1, col+1) && (col<cols-1)) {neighbors.add(new Neighbor(getObject(row-1, col+1), xx));}
			if (!isEmpty(row-1, col+2) && (col<cols-2)) {neighbors.add(new Neighbor(getObject(row-1, col+2), xx));}
		}
		
		if (!isEmpty(row, col-2) && (col>1))            {neighbors.add(new Neighbor(getObject(row, col-2), xx));}
		if (!isEmpty(row, col-1) && (col>0))            {neighbors.add(new Neighbor(getObject(row, col-1), xx));}
		if (!isEmpty(row, col+1) && (col<cols-1))       {neighbors.add(new Neighbor(getObject(row, col+1), xx));}
		if (!isEmpty(row, col+2) && (col<cols-2))       {neighbors.add(new Neighbor(getObject(row, col+2), xx));}
		
		if (row<rows-1) {
			if (!isEmpty(row+1, col-2) && (col>1))      {neighbors.add(new Neighbor(getObject(row+1, col-2), xx));}
			if (!isEmpty(row+1, col-1) && (col>0))      {neighbors.add(new Neighbor(getObject(row+1, col-1), xx));}
			if (!isEmpty(row+1, col))                   {neighbors.add(new Neighbor(getObject(row+1, col), xx));}
			if (!isEmpty(row+1, col+1) && (col<cols-1)) {neighbors.add(new Neighbor(getObject(row+1, col+1), xx));}
			if (!isEmpty(row+1, col+2) && (col<cols-2)) {neighbors.add(new Neighbor(getObject(row+1, col+2), xx));}
		}
		
		if (row<rows-2) {
			if (!isEmpty(row+2, col-2) && (col>1))      {neighbors.add(new Neighbor(getObject(row+2, col-2), xx));}
			if (!isEmpty(row+2, col-1) && (col>0))      {neighbors.add(new Neighbor(getObject(row+2, col-1), xx));}
			if (!isEmpty(row+2, col))                   {neighbors.add(new Neighbor(getObject(row+2, col), xx));}
			if (!isEmpty(row+2, col+1) && (col<cols-1)) {neighbors.add(new Neighbor(getObject(row+2, col+1), xx));}
			if (!isEmpty(row+2, col+2) && (col<cols-2)) {neighbors.add(new Neighbor(getObject(row+2, col+2), xx));}
		}
		
		return neighbors;
	}
	*/
	
	
	private Double _maxNeighborhoodSize(Integer perception) {
		if (perception == 0) {
			return 0.0;
		}
		else {
			Double mns = (perception * 8) + maxNeighborhoodSize(perception - 1);
			return mns;
		}
	}
	
	private Map<Integer, Double> ns = new HashMap<Integer, Double>();
	private Double maxNeighborhoodSize(Integer perception) {
		if (ns.containsKey(perception)) {
			return ns.get(perception);
		}
		else {
			Double maxNS = _maxNeighborhoodSize(perception);
			ns.put(perception, maxNS);
			return maxNS;
		}
	}
	
	public Double getSimilarityNeighbors(Object o, List<Neighbor> neighbors) {
		try {
			double distance = 0D;
			double count = 0D;
			double totalWeight = 0D;
			
			for (Neighbor neighbor : neighbors) {
				Double dist = similarityBetweenTwo(o, neighbor.getObject());
				if (dist != null) {
					distance += neighbor.getWeight() * dist;
					totalWeight += neighbor.getWeight();
					count ++;
				}
			}
			//return (distance / count);
			//return (distance / neighbors.size());
			Double res;
			if (totalWeight != 0.0) {
				log.debug(neighbors + " dist:" + distance + " count:" + count + " totalWeight:" + totalWeight);
				//res = (distance / totalWeight);
				//res = (distance / count);
				res = ( distance / (Math.log(count) + 1) );
			}
			else {
				res = 0.0;
			}
			return res;
			
		}
		catch (Throwable e) {
			e.printStackTrace();
			return 0.0;
		}
		
		
	}
	
	private Double similarityBetweenTwo(Object o, Object neighbor) {
		if (distanceDAO != null) {
			return distanceDAO.getDistance(o, neighbor);
		}
		else {
			return 0.0;
		}
	}
	
}
